from __future__ import print_function
from __future__ import unicode_literals
from __future__ import division

import logging

from kharlia.core import GetApp, CreateTasklet, Sleep, Yield, CObject, CWeakRefDescr
from kharlia.engine.actor import (ComponentClass, AComponent, APlayerController,
                                  ATransform, ACollider, AMovement, ACamera, ASprite,
                                  ASpriteAnimation, APawn, ENetRole)
from kharlia.engine.level import FTileData
from kharlia.engine.world import CGame
from kharlia.engine.base import GetEngine, FSizeI, FVector3F, FVector2I, FRectI, FRectF
from kharlia.engine.client import FTexture, CSpriteAniDef

__all__ = [
    'ATestGoal',
    'APlayerSpawn',
    'ATestPlayerController',
    'CTestGame'
    ]

GLogger = None
GCompleted = 0
GTicks = 0
GDidTests = False

def ReportMRO(cls, out=print):
    if not isinstance(cls, type):
        raise TypeError('not a class')
    out('MRO for: {0}.{1}'.format(cls.__module__, cls.__name__))
    for i, e in enumerate(cls.__mro__):
        out('{0}: {1}'.format(i, str(e)))

def ReportNativeObjects(out=print):
    out('== BEGIN OBJECT LIST ==')
    for i in CObject.IterObjects():
        out(str(i))
    out('== END OBJECT LIST ==')

def PrintTileData(td, out=print):
    tdl = td.ToList()
    i = 0
    out('== FTileData ==')
    for y in xrange(td.Size.Height):
        line = ''
        for x in xrange(td.Size.Width):
            line += str(tdl[i]) + ', '
            i += 1
        out(line)
    out('== End FTileData ==')

#def MessageReader(conn):
#    GLogger.debug('READING MESSAGE...')
#    msg = conn.ReadMessage()
#    GLogger.debug('READ MESSAGE: %s', msg)

#class CNetTestListener(net.CNetListener):
#    def OnConnectionAccepted(self, conn):
#        core.CreateTasklet(MessageReader)(conn)

def InitLogger():
    global GLogger
    if not GLogger:
        GLogger = logging.getLogger('testgame')

@ComponentClass
class ATestGoal(AComponent):
    def __init__(self):
        AComponent.__init__(self)

    def OnBeganTouch(self, other):
        pawn = other.Actor.Pawn
        if pawn and isinstance(pawn.Controller, APlayerController):
            self.Level.Game.OnPCTouchedGoal(pawn.Controller)

@ComponentClass
class APlayerSpawn(AComponent):
    pass

class CTestGame(CGame):
    LocalPC = CWeakRefDescr('_LocalPC')
    TestPC = CWeakRefDescr('_TestPC')
    Engine = CWeakRefDescr('_Engine')
    Goal = CWeakRefDescr('_Goal')

    def __init__(self, world):
        InitLogger()
        CGame.__init__(self, world)
        self.PlayerControllerClass = ATestPlayerController
        self.GoalClasses = (ATransform, ACollider, ATestGoal)
        self.GoalLevelName = None
        self.GoalPosition = None
        self.GoalBounds = None
        self._TestPC = None
        self._LocalPC = None
        self._Engine = None
        self._Goal = None
        self.TDAs = None
        self.TDAPaths = []

        #config = GetApp().ConfigRoot
        #self.TDAPaths = list(config['TestGame']['TDAPaths'].itervalues())
    
    def Init(self):
        GLogger.info('initializing test game')
        
        if not self.World.Engine:
            raise RuntimeError('valid engine required for initialization')
        self.Engine = self.World.Engine

        CGame.Init(self)

        self.KeyStates = {}
        
    def Exit(self):
        GLogger.info('exiting test game')
        
        CGame.Exit(self)
        
    def LoadTDAs(self):
        resolve = GetEngine().ResolvePath
        for p in self.TDAPaths:
            rp = resolve(p)

    def Login(self, player):
        '''
        Perform player login; makes the test PC the first PC to login.
        '''
        pc = super(CTestGame, self).Login(player)
        assert pc
        if not self.TestPC:
            self.TestPC = pc
            GLogger.debug('Set test PC to %s', pc)
            #if not pc.Actor.Network:
            #    pc.Actor.AddComponent(ANetwork())
        return pc
    
    def FindSpawnPoint(self):
        '''Locate the first instance of APlayerSpawn in the world.'''
        for level in self.World.IterLevels():
            for actor, comp in level.IterComponents(APlayerSpawn):
                return actor, comp
        return None, None

    def PostLogin(self, pc):
        '''
        Spawn the PC's pawn.
        '''
        super(CTestGame, self).PostLogin(pc)

        # Stop if we're loading a saved game and an existing PC and pawn
        # was loaded
        if pc.Pawn:
            return

        act = self.CreatePlayerPawn(pc)
        lev = act.Level

        # Spawn actor collision test
        if 1:
            if not lev.GetActorByName('TestCollider'):
                act2 = lev.CreateActor((ATransform,
                                        ACollider,
                                        ASprite),
                                        name='TestCollider')
                #act2.Tag = 'TestCollider'
                act2.IsTransient = True
                col = act2.Collider
                col.Bounds = FRectF(-1, -1, 4, 4)
                col.CollideActors = True
                col.BlockActors = False
                col.CollisionLayer = -1
                act2.Transform.Move((10.0, 5.0, 0.0))
                act2.RemoteRole = ENetRole.SIM_PROXY
                #if self.World.NetMode != engine.NM_CLIENT:
                #    act2.AddComponent(engine.ANetwork())

        # Debug testing
        #global GDidTests

        #if not GDidTests:
        #    GDidTests = True
        #    core.CreateTasklet(PerformTests)()

    def CreatePlayerPawn(self, pc, level=None, position=None):
        # Locate a spawn point
        if not level:
            spawnact, spawn = self.FindSpawnPoint()
            if spawn is None:
                return
            assert spawnact.Transform
            level = spawnact.Level
            if not position:
                position = spawnact.Transform.Position

        # Spawn the PC's pawn
        comps = (
            ATransform,
            AMovement,
            ACollider,
            ACamera,
            ASpriteAnimation,
            APawn
            )
                    
        act = level.CreateActor(comps, pc.Actor, 'TestActor')
                
        trans = act.Transform
        trans.Position = position
                
        col = act.Collider
        col.Bounds = FRectF(0, 0, 2, 2)
        col.CollideTiles = True
        col.CollideActors = True
        col.BlockActors = True
        col.CollisionLayer = -1

        resmgr = GetEngine().ResourceManager
        img = resmgr.CreateResource(FTexture, b'images/anitest.png')
        adef = resmgr.CreateResource(CSpriteAniDef, b'TestAni.kad')
        img.Load()
        adef.Load()
        act.Render.Image = img
        act.Render.Definition = adef
        act.Render.ShouldMapToDirections = True
        self.SetupPCPawn(pc, act)
        return act   
        
    def SetupPCPawn(self, pc, pawnact):
        pawn = pawnact.Pawn
        assert pc and pawn
        pc.Possess(pawn)
        pc.Camera = pawnact.Camera
        return pawnact

    def OnPCTouchedGoal(self, pc):
        GLogger.debug('PC touched goal!: %s', pc)

    def CreateGoal(self):
        '''Create the goal for the game.
        
        Returns error string or None.
        '''
        if not self.GoalLevelName:
            return 'Level unspecified'

        level = self.World.GetLevel(self.GoalLevelName)
        if not level:
            return 'Level not found'

        assert self.GoalPosition and self.GoalSize

        goal = level.CreateActor(self.GoalClasses)
        goal.Transform.Position = self.GoalPosition
        goal.Collider.CollideActors = True
        goal.Collider.BlockActors = False
        goal.Collider.Bounds = self.GoalBounds
        self.Goal = goal
        return None


class ATestPlayerController(APlayerController):
    def __init__(self):
        APlayerController.__init__(self)
        
    def Init(self, owner):
        super(ATestPlayerController, self).Init(owner)
        
        GLogger.debug('initialized test player controller')

from kharlia import core_pb2
        
class CTestA(object):
    def __init__(self, x=None, y=None, z=None):
        self.x = x
        self.y = y
        self.z = z
        self.r1 = None
        self.r2 = None
    
    @classmethod
    def __pbctor__(cls, package, load, s):
        m = core_pb2.CTestAInit.FromString(s)
        return cls(m.x, m.y, m.z)
    
    def __pbargs__(self, package, save):
        m = core_pb2.CTestAInit()
        m.x, m.y, m.z = self.x, self.y, self.z
        return m
        #return None
        
    def __pbsave__(self, package, save):
        m = core_pb2.CTestA()
        #m.x = self.x
        #m.y = self.y
        #m.z = self.z
        m.r1 = save(self.r1)
        m.r2 = save(self.r2)
        return m
    
    def __pbload__(self, package, load, s):
        m = core_pb2.CTestA.FromString(s)
        self.r1 = load(m.r1)
        self.r2 = load(m.r2)

def DumpActorReferences(world, check_comps=True):
    import gc
    print('Reference dump (Actors):')

    def printns(depth=0, *args):
        print('  ' * depth, *args, sep='')

    def filterdupes(alist):
        blist = []
        for item in alist:
            if item not in blist:
                blist.append(item)
        return blist

    for level in world.IterLevels():
        printns(1, level.Name, ' (', level.GUID, '):')
        for actor in level.IterActors():
            printns(2, actor, ':')
            # Referrers
            rs = []
            rs.extend(gc.get_referrers(actor))
            if check_comps:
                for comp in actor.IterComponents():
                    rs.extend(gc.get_referrers(comp))
            rs = filterdupes(rs)
            printns(3, 'Referrers:')
            for r in rs:
                printns(4, type(r), ' = ', r)
            # Referents
            rs = []
            rs.extend(gc.get_referents(actor))
            if check_comps:
                for comp in actor.IterComponents():
                    rs.extend(gc.get_referents(comp))
            rs = filterdupes(rs)
            printns(3, 'Referents:')
            for r in rs:
                printns(4, type(r), ' = ', r)

# NOTE: This function is used for running various tests. A lot of the code might be
#       for changed, or no longer used code and can be obsolete.
def PerformTests():
    ensvc = GetApp().ServiceManager.GetServiceNoFail('kharlia.engine.main.CEngineService')
    while not ensvc.IsStarted:
        Sleep(0.5)
    eng = ensvc.Engine
    InitLogger()
    
    # protobuf packaging test
    if 0:
        print('PERFORMING PACKAGE TEST')
        from cStringIO import StringIO

        # Manual
        if 0:
            # Create test objects with circular refs
            a = CTestA(3, 5, 6)
            b = CTestA(9, 9, 8)
            a.r1 = b
            b.r1 = a

            # Save to StringIO object
            pk = core.CPackageSave(None, a)
            s = StringIO()
            pk.Save(s)
            print('SAVED!')
            print('result:', s.getvalue().encode('string_escape'))
            pk.Report() # print some details
            b.r1 = None
            a.r1 = None
        
            # Load from StringIO
            sl = StringIO(s.getvalue())
            pkl = core.CPackageLoad(None)
            obj = pkl.Load(sl)
            print('result:', obj)
        
            # Verify
            assert obj.x == 3 and obj.y == 5 and obj.z == 6
            assert obj.r1 is not None and obj.r1 is not obj
            assert obj.r1.r1 is obj
            obj2 = obj.r1
            assert obj2.x == 9 and obj2.y == 9 and obj2.z == 8
            obj2.r1 = None
            obj.r1 = None

    # Collision polygon testing
    if 0:
        lev = eng.World.GetLevel('TestLevel')
        assert lev
        
        polys = lev.BuildTileCollisionPolygons(lev.GetTileGroup(0).GetLayer(0))
        if polys:
            print('POLYGONS:')
            for p in polys:
                print(' ', p)

    # Game pickle testing
    if 0:
        game = eng.World.Game
        print('PICKLING GAME', game)
        out = cstringio.StringIO()
        engine.CGame.Pickle(game, out)
        outs = out.getvalue()
        print('OUT LEN:', len(outs))
        #file = io.open('game.pickle', 'wb')
        #file.write(outs)
        #file.close()
        core.Sleep(1.0)
        print('UNPICKLING GAME')
        out.seek(0)
        game2 = engine.CGame.Unpickle(eng.World, out)
        print('UNPICKLED:')
        print('WORLD:', game2.World)
        print('REALTIME:', game2.RealTime)
        print('PCCLASS:', game2.PlayerControllerClass)

    # New network component testing
    if 0:
        net = engine.ANetwork()
        act.AddComponent(net)
        assert net
        print('ACTOR LEN:', len(act))
        net.InitPythonRep()
        for i in xrange(3):
            start = time.clock()
            rn = net.ReplicatePython()
            elapsed = (time.clock() - start) * 1000
            print('RESULTTIME (MS):', elapsed)
            if rn is not None:
                print('RESULTS:')
                for rcls in rn:
                    print(rcls)
            else:
                print('RESULTS: None')

    # CTasker improvements test
    if 0:
        tasker = app.Tasker
        tlist = tasker.GetTaskletList(0)
        print('tasklet list:')
        for i in tlist:
            print(i)
        print('end list')
            
    # Boost.Python unicode type wrapper test
    if 0:
        from kharlia._core import TestUnicode
        TestUnicode('lol ninja pancakes')
                
    # Special Extract test
    if 0:
        from kharlia._core import TestExtract
        TestExtract(app)

    # TMX Load testing
    if 0:
        sleep(1.0)
        file = open(r'data\levels\TestLevel.tmx')
        engine.LoadTMX(file, world)
                
    # Actor spawn testing
    if 0:
        sleep(2.0)
        comps = (
            engine.ATransform,
            engine.AMovement,
            engine.ACollider,
            engine.ACamera,
            #engine.ARender,
            engine.APawn
            )
        GLogger.info('BEGIN ACTOR SPAWNING')
        #engine.GLogger.setLevel(logging.INFO)
        spawns = 3000
        perloop = 10
        start = time.clock()
        c = 0
        for i in xrange(spawns):
            lev.CreateActor(comps)
            if c > perloop:
                yield_()
                c = 0
            else:
                c += 1
        elapsed = time.clock() - start
        elapsedper = elapsed / spawns
        GLogger.info('END ACTOR SPAWNING: %s (%s per actor)', elapsed, elapsedper)

    # Actor variable data
    if 0:
        print('ACTOR VARS:')
        dvs = []
        for k, v in cam.__dict__.iteritems():
            dvs.append(k)
            
        for c in type(cam).mro():
            for k, v in c.__dict__.iteritems():
                if (hasattr(v, '__get__') and
                    (hasattr(v, '__set__') or hasattr(v, '__delete__')) and
                    not (k.startswith('__') and k.endswith('__'))):
                    dvs.append(k)

        for i in dvs:
            print(i)

    # Actor reference testing
    #   Involves finding references to other actors in and outside
    #   of the level
    if 0:
        import gc
        sleep(1.0)
        print('Beginning actor reference testing')

        target = lev

        actors = []
        for i in target.IterActors():
            refs = []
            for e in gc.get_referents(i):
                if isinstance(e, engine.AActor):
                    refs.append(e)
            actors.append((i, refs))

        print('actor refs found:')
        for actor, refs in actors:
            print(actor, '=', refs)


    # GC Reference testing
    if 0:
        import gc
        from kharlia._core import TestFileIODump
        print('GC enabled:', gc.isenabled())
        sleep(4.0)
        print('DUMPING GC INFO')

        dumptarget = lev

        outlines = []
        outlines.append('=== referrers ===')
        for i in gc.get_referrers(dumptarget):
            outlines.append(''.join((str(type(i)), ' = ', str(i))))
        outlines.append('=== referrents ===')
        for i in gc.get_referents(dumptarget):
            outlines.append(''.join((str(type(i)), ' = ', str(i))))
            #print('referents:', i)
        outlines = '\n'.join(outlines)
        TestFileIODump('gcinfo.txt', outlines)
        print('FINISHED DUMP')

    # Native GC macro testing
    if 0:
        sleep(2.0)
        import gc
        gc.set_debug(gc.DEBUG_STATS | gc.DEBUG_COLLECTABLE | \
                        gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_INSTANCES | \
                        gc.DEBUG_OBJECTS)
        act10 = lev.CreateActor(ACamera, name='GCTest1')
        act11 = lev.CreateActor(ACamera, name='GCTest2')
        act10.Target = act11
        act11.Target = act10
        lev.DestroyActor(act10)
        lev.DestroyActor(act11)
        del act10
        del act11
        sleep(2.0)
        while gc.collect():
            pass

    # Layer testing
    if 0:
        #info = lev.GetLayer(0)
        #info.Visible = False
        #sleep(2.0)
        #info.Visible = True
        info1 = lev.CreateLayer(1)
        sleep(2.0)
        info1.Alpha = 128

    # Actor creation Testing
    if 0:
        sleep(3.0)
        start = time.clock()
        act3 = lev.CreateActor(AActor, owner=act,
                                pos=FVector3F(2.0, 2.0, 2.0))
        elapsed = time.clock() - start
        GLogger.debug('created actor in %s miliseconds', elapsed * 1000)

    # Stackless IO Testing
    if 0:
        testnum = 50
        sleep(2.0)

        def iotest():
            global GCompleted
            start = time.clock()
            filestr = core.native.TestFileIO('jsontest.json')
            elapsed = time.clock() - start
            print('FILE IO ELAPSED TIME:', elapsed)
            print('FILE IO RESULT LEN:', len(filestr))
            GCompleted += 1

        starto = time.clock()
        for i in xrange(testnum):
            app.CreateTasklet(iotest)()
        while GCompleted < testnum:
            GTicks += 1
            print('TICK:', GTicks)
            yield_()

        elapsedo = time.clock() - starto
        print('TOTAL COMPLETION TIME:', elapsedo)
        print('TICKS NEEDED TO COMPLETE:', GTicks)
        #print('FILE IO RESULT:', filestr)
        rpt = core.native.StatReportDispatch()
        print('DISPATCH REPORT: count=%s, totaltime=%s, avgtime=%s' % rpt)
        dumpstr = 'lol testing dump something'
        core.native.TestFileIODump('iotest_dump.txt', dumpstr)

    # Async IO Testing
    if 0:
        def CBTest():
            print('CALLED THE CALLBACK!')

        dumpstr = 'lol testing dump something'
        core.native.TestFileIODumpCB('test/iotest_dumpcb.txt', dumpstr, CBTest)
        #core.native.TestFileIODumpCBC('test/iotest_dumpcb.txt', dumpstr)

    # Tiledata copy testing
    if 0: 
        td = FTileData(FSizeI(4, 4), (1, 2, 3, 4,
                                        5, 6, 7, 8,
                                        9, 10, 11, 12,
                                        13, 14, 15, 16))
        tdl = td.ToList()
        #print(tdl)
        PrintTileData(td)
        td2 = FTileData(FSizeI(4, 4), (20, 21, 22, 23,
                                        24, 25, 26, 27,
                                        28, 29, 30, 31,
                                        32, 33, 34, 35))
        PrintTileData(td2)
        td2.CopyTiles(FVector2I(0, 0), FRectI(0, 0, 4, 4), td)
        PrintTileData(td2)

    # Replication testing
    if 0:
        #act.NetDirty = True
        #svcmgr['replication'].RegisterActor(act)
        rep = engine.CActorReplicator()
        rep.BindActor(cam)

        start = time.clock()
        rep.ReplicatePython()
        elapsed = time.clock() - start
        print('elapsed:', elapsed)

        sleep(1.0)

        start = time.clock()
        rep.ReplicatePython()
        elapsed = time.clock() - start
        print('elapsed:', elapsed)

        sleep(1.0)

        cam.Dir = engine.DIR_WEST
        cam.TestVar = 'superpancake'

        start = time.clock()
        rep.ReplicatePython()
        elapsed = time.clock() - start
        print('elapsed:', elapsed)

    # Native replication testing
    if 0:
        start = time.clock()
        rstr = rep.ReplicateNative()
        elapsed = time.clock() - start
        print('rtime:', elapsed * 1000)
        if rstr is not None:
            print('rstrlen:', len(rstr))
        print('rstr:', rstr)

        cam.Position = (3.0, 4.0, 0.0)
        cam.TargetOffset = (1.0, 1.0, 0.0)

        start = time.clock()
        rstr = rep.ReplicateNative()
        elapsed = time.clock() - start
        print('rtime:', elapsed * 1000)
        if rstr is not None:
            print('rstrlen:', len(rstr))
        print('rstr:', rstr)

        start = time.clock()
        rstr = rep.ReplicateNative()
        elapsed = time.clock() - start
        print('rtime:', elapsed * 1000)
        if rstr is not None:
            print('rstrlen:', len(rstr))
        print('rstr:', rstr)

        start = time.clock()
        rstr = rep.ReplicateNative()
        elapsed = time.clock() - start
        print('rtime:', elapsed * 1000)
        if rstr is not None:
            print('rstrlen:', len(rstr))
        print('rstr:', rstr)

    # Basic pickle testing
    if 0:
        td = FTileData(FSizeI(4, 4), (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16))
        print(td)
        ptd = pickle.dumps(td, pickle.HIGHEST_PROTOCOL)
        print('DUMP SIZE:', len(ptd))
        td2 = pickle.loads(ptd)
        print(td2)

    # Level pickle testing
    if 0:
        import zlib

        # Simpler tilegroup pickle testing
        if 0:
            tg = lev.GetTileGroup(0)
            print(tg)
            ptg = pickle.dumps(tg, pickle.HIGHEST_PROTOCOL)
            print('DUMP SIZE:', len(ptg))
            tg2 = pickle.loads(ptg)
            print(tg2)
            print('LC:', tg2.LayerCount)
            print('SZ:', tg2.Size)
            print('L0 Index:', tg2.GetLayer(0).Index)
            print('L0 TileData:', tg2.GetLayer(0).TileData)

        print(lev)
        plev = engine.CWorld.PickleLevel(lev)
        print('LEVEL DUMP SIZE:', len(plev))
        zplev = zlib.compress(plev)
        print('COMPRESSED LEVEL DUMP SIZE:', len(zplev))
        dzplev = zlib.decompress(zplev)
        print('DECOMPRESSED LEVEL DUMP SIZE:', len(dzplev))
        world.DestroyLevel(lev)
        print('DESTROYED LEVEL')

        sleep(2.0)

        print('LOADING LEVEL...')
        lev2 = engine.CWorld.UnpickleLevel(world, plev)
        print('LOADED LEVEL')
        print(lev2)
        if 1:
            # Transient camera
            cam2 = lev2.CreateActor(ACamera, name='MainCameraX', pos=(5, 5, 5))
            print('CAM2:', cam2)
            eng.ClientCamera = cam2
            eng.TestActor = lev2.GetActor('AActor_1')
            cam2.Target = eng.TestActor

        else:
            # Non-Transient camera
            cam2 = world.GetActor('MainCamera')
            print('CAM2:', cam2)
            eng.ClientCamera = cam2
            eng.TestActor = lev2.GetActor('AActor_1')


        #print(cam)
        #pcam = pickle.dumps(cam, pickle.HIGHEST_PROTOCOL)
        #print('DUMP SIZE:', len(pcam))
        #cam2 = pickle.loads(pcam)
        #print(cam2)

    # Level package
    if 0:
        engine.CWorld.SaveLevelPackage(lev, 'test/levelpackage.khl')

    # World pickle
    if 0:
        from cStringIO import StringIO

        core.Sleep(2.0)
        print('BEGINNING WORLD PICKLE')
        world = eng.World
        pfile = StringIO()

        engine.CWorld.PickleWorld(world, pfile)

        print('FINISHED WORLD PICKLE:', len(pfile.getvalue()))

    # Access CEGUI objects
    if 0:
        client = eng.Client
        assert client

        core.Sleep(3.0)
        dt = client.GetWindow('debugText')
        print('GOT WINDOW:', dt)
        print('WIN TEXT:', dt.Text)

    # RakNet testing (start client)
    if 0:
        import subprocess

        app = core.GetApp()

        wxsvc = app.ServiceManager.GetServiceNoFail('kharlia.wxinter.CWXService')
        frame = wxsvc.TopWindow
        assert frame

        if '--raktest' in app.Args:
            world = eng.World
            lev = world.GetLevel('TestLevel')
            assert not world.Game

            frame.Title = frame.Title + ' (NETWORK CLIENT)'
            GLogger.info('NETWORK CLIENT STARTED')
            GLogger.info('attempting connnection to server')
            #world.NetConnect('localhost', 60500)
            eng.Connect('localhost', 60500)

            #if 1:
            #    nact = world.Game.TestPC.Pawn.Actor
                #if not nact.Network:
                #    nact.AddComponent(engine.ANetwork())
                    
        else:
            world = eng.World
            #world.NetRepRate = 5
            lev = world.GetLevel('TestLevel')

            frame.Title = frame.Title + ' (NETWORK SERVER)'
            GLogger.info('NETWORK SERVER STARTED')
            world.NetListen()
            GLogger.info('server now listening')

            obj = subprocess.Popen('cmd /C start /WAIT bin\\Kharlia_d.exe --raktest --nogame')

            if 1:
                core.Sleep(5.0)
                print('INITIALIZING NETWORKING FOR TEST ACTOR')
                pcact = world.Game.TestPC.Actor
                nact = world.Game.TestPC.Pawn.Actor
                pcact.Tag = 'Net PC'
                nact.Tag = 'Net Pawn'
                print('NET PC:', pcact)
                print('NET PAWN:', nact)
                #if not pcact.Network:
                #    pcact.AddComponent(engine.ANetwork())
                #if not nact.Network:
                #    nact.AddComponent(engine.ANetwork())
                pcact.RemoteRole = ENetRole.AUTO_PROXY
                nact.RemoteRole = ENetRole.AUTO_PROXY
                nact.Transform.Position = (1.0, 1.0, 1.0)
                #world.NetSendDebugMessage('HELLO, HOW ARE YOU?!')
                #nact = lev.CreateActor((engine.ATransform,
                #                        engine.ASprite,
                #                        engine.ACollider,
                #                        engine.ANetwork))
                #nact.Network.RemoteRole = engine.ROLE_SIM_PROXY
                #nact.Collider.Bounds = FRectF(0, 0, 2, 2)
                #nact.Collider.CollideTiles = True
                #nact.Collider.CollideActors = True
                #eng.TestActor = nact
                    
                core.Sleep(3.0)
                #GLogger.info('beginning RPC send')
                #world.NetSendActorRPC(True, nact, 'DisplayRPCMessage', 'hello 123')
                #nact.ClientDisplayRPCMessage('lol pancakes')
                #nact.ServerDisplayRPCMessage('server to server')
                #GLogger.info('finished RPC send')
                    
                #sleep(5.0)
                #nact.RemoteRole = engine.ROLE_NONE

    # New reference checking
    if 0:
        import gc
        world = eng.World
        
        DumpActorReferences(world)

    # RakNet testing (start server) (NEW CODE)
    if 0:
        import subprocess

        app = core.GetApp()

        wxsvc = app.ServiceManager.GetServiceNoFail('kharlia.wxinter.CWXService')
        frame = wxsvc.TopWindow
        assert frame

        if '--raktest' in app.Args:
            world = eng.World
            #world.NetRepRate = 5
            lev = world.GetLevel('TestLevel')
            frame.Title = frame.Title + ' (NETWORK SERVER)'
            GLogger.info('NETWORK SERVER STARTED')
            world.NetListen()
            GLogger.info('server now listening')

            if 1:
                #core.Sleep(5.0)
                print('INITIALIZING NETWORKING FOR TEST ACTOR')
                pcact = world.Game.TestPC.Actor
                pcact.Tag = 'Net PC'
                if not pcact.Network:
                    pcact.AddComponent(engine.ANetwork())
                pcact.RemoteRole = engine.ROLE_AUTO_PROXY
                nact = world.Game.TestPC.Pawn.Actor
                nact.Tag = 'Net Pawn'
                if not nact.Network:
                    nact.AddComponent(engine.ANetwork())
                nact.Transform.Position = (1.0, 1.0, 0.0)
                nact.RemoteRole = engine.ROLE_AUTO_PROXY
                    
        else:
            world = eng.World
            lev = world.GetLevel('TestLevel')
            if world.Game:
                world.StopGame()
            eng.LocalPlayer.Controller.Player = None
            eng.LocalPlayer.Controller = None
            eng.Client.Camera = None

            frame.Title = frame.Title + ' (NETWORK CLIENT)'
            
            obj = subprocess.Popen('cmd /C start /WAIT bin\\Kharlia_d.exe --raktest')

            GLogger.info('NETWORK CLIENT STARTED')
            core.Sleep(5.0) # Wait for server to be ready
            GLogger.info('attempting connnection to server')
            #world.NetConnect('localhost', 60500)
            eng.Connect('localhost', 60500)

            core.Sleep(2.0)

            for act in lev.IterActors():
                if act.Network and act.Network.Role < engine.ROLE_AUTHORITY:
                    print('FOUND SERVER ACTOR:', act)
                    print('PC:', act.PlayerController)
                    print('PAWN:', act.Pawn)
                    print('PAWNC:', act.Pawn.Controller)

            #if 1:
            #    nact = lev.GetActorByTag('TestActor')
                #if not nact.Network:
                #    nact.AddComponent(engine.ANetwork())

    # Simulated function
    if 0:
        act.SimTest()
        act.SimTestN()
        act.Role = engine.ROLE_SIM_PROXY
        act.SimTest()
        act.SimTestN()
        # TODO: Not setting the Role back causes slight delay
        #       on exit. Wtf?
        act.Role = engine.ROLE_AUTHORITY

    # Interactive console
    if 0:
        core.CreateTasklet(RunInteractive)()

    # TDA module
    if 0:
        from kharlia import tda

        data, conflicts = tda.LoadODSDir(ur'data\tdas')
        merges = tda.Merge(data, data['TDA_Base'])
        btda = data['TDA_Base']
        print('TDA NAMES:')
        for row in btda.Rows:
            print(row)
        print('NAMES ID:', btda['Names', 'ID'])
        print('NAMES:')
        for row in data['Names'].Rows:
            print(row)
        #print('VALUE:', tda['blah', 'ID'])
        #print('VALUED:', tda.GetRowAsDict('blah'))

    # Game loading and saving
    if 0:
        Sleep(2.0)
        eng.SaveGame(5)
        eng.World.StopGame()
        eng.World.ClearLevels(True)
        eng.World.Clear()
        eng.World = None
        Sleep(1.0)
        eng.LoadSavedGame(5)

    # Sprite ani def
    if 0:
        from kharlia.engine.client import CSpriteAniDef

        d = eng.ResourceManager.CreateResource(CSpriteAniDef, b'TestAni.kad')
        d.Engine = eng
        #d = CSpriteAniDef(b'data/TestAni.kad')
        d.Load()
        print('LOADED:', d)
        print('LISTS:')
        for i, f in enumerate(d.IterFrameLists()):
            print(' ', i, '=', f)

    ## END TESTING

import code, sys, wx

from wx.py import pseudo

class ConsoleFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, parent=None, title='Console')
        #style = wx.TE_PROCESS_ENTER | wx.TE_PROCESS_TAB | wx.TE_MULTILINE
        style = wx.TE_MULTILINE | wx.TE_RICH2
        self.TextCtrl = wx.TextCtrl(parent=self, style=style)
        font = wx.Font(pointSize=10,
                       family=wx.FONTFAMILY_MODERN,
                       style=wx.FONTSTYLE_NORMAL,
                       weight=wx.FONTWEIGHT_NORMAL)
        self.TextCtrl.SetDefaultStyle(wx.TextAttr(wx.BLACK, font=font))

        self.ReadingLine = False
        self.LastLine = None
        self.LinePrompt = None

        self.Printing = False
        self.TabIndent = '    '
        self.LastOut = None

        self.Console = KharliaInteractiveConsole(self)

        self.TextCtrl.Bind(wx.EVT_TEXT, self.OnTextCtrlChanged)
        self.TextCtrl.Bind(wx.EVT_CHAR, self.OnTextCtrlChar)
        self.Bind(wx.EVT_CLOSE, self.OnClose)

    def RedirectStdOut(self):
        self.LastOut = sys.stdout
        sys.stdout = pseudo.PseudoFileOut(self.Print)

    def RestoreStdOut(self):
        if self.LastOut:
            sys.stdout = self.LastOut

    def Interact(self):
        self.Console.interact()

    def OnClose(self, event):
        self.Console.exit = True
        self.EndReadLine()
        self.RestoreStdOut()
        self.Destroy()

    def OnTextCtrlChar(self, event):
        if event.GetKeyCode() == wx.WXK_TAB:
            self.Print(self.TabIndent)
        else:
            event.Skip()

    def OnTextCtrlChanged(self, event):
        if self.Printing or not self.ReadingLine:
            return
        value = self.TextCtrl.GetValue()
        if len(value) < 1 or value[-1] != '\n':
            return
        lbegin = value.rfind('\n', 0, -1) + 1
        line = value[lbegin:-1]
        assert line.find('\n') == -1
        self.LastLine = line

    def ReadLine(self, prompt=''):
        if self.ReadingLine:
            if self.LastLine is not None:
                line = self.LastLine
                if line.startswith(self.LinePrompt):
                    line = line[len(self.LinePrompt):]
                self.EndReadLine()
                return False, line
            else:
                return False, None
        else:
            self.ReadingLine = True
            self.LinePrompt = unicode(prompt)
            self.Print(self.LinePrompt)
            return True, None

    def EndReadLine(self):
        if self.ReadingLine:
            self.LastLine = None
            self.LinePrompt = None
            self.ReadingLine = False

    def OnEnter(self, event):
        pass

    def Print(self, data=None, line=False):
        self.Printing = True
        if data is None and line:
            self.TextCtrl.AppendText('\n')
        else:
            if not isinstance(data, unicode):
                data = unicode(data)
            if line:
                data += '\n'
            self.TextCtrl.AppendText(data)
        self.Printing = False

class KharliaInteractiveConsole(code.InteractiveConsole):
    def __init__(self, frame, *args, **kwds):
        code.InteractiveConsole.__init__(self, *args, **kwds)
        self.exit = False
        self.frame = frame

    def setexit(self, value):
        self.exit = value

    def interact(self, banner=None):
        try:
            sys.ps1
        except AttributeError:
            sys.ps1 = ">>> "
        try:
            sys.ps2
        except AttributeError:
            sys.ps2 = "... "
        cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
        if banner is None:
            self.write("Python %s on %s\n%s\n(%s)\n" %
                        (sys.version, sys.platform, cprt,
                        self.__class__.__name__))
        else:
            self.write("%s\n" % str(banner))
        more = 0
        while not self.exit:
            try:
                if more:
                    prompt = sys.ps2
                else:
                    prompt = sys.ps1
                try:
                    line = self.raw_input(prompt)
                    # Can be None if sys.stdin was redefined
                    encoding = getattr(sys.stdin, "encoding", None)
                    if encoding and not isinstance(line, unicode):
                        line = line.decode(encoding)
                except EOFError:
                    self.write("\n")
                    break
                else:
                    more = self.push(line)
            except KeyboardInterrupt:
                self.write("\nKeyboardInterrupt\n")
                self.resetbuffer()
                more = 0
            
    def write(self, data):
        self.frame.Print(data)

    def raw_input(self, prompt=''):
        started, line = self.frame.ReadLine(prompt)
        assert started
        while not self.exit:
            started, line = self.frame.ReadLine()
            assert not started
            if line is not None:
                return str(line)
            core.Yield()
        return ''

def RunInteractive():
    wxsvc = core.GetApp().ServiceManager.GetServiceNoFail('kharlia.wxinter.CWXService')
    if not wxsvc.IsStarted:
        core.Yield()

    frame = ConsoleFrame()
    frame.Show()
    frame.RedirectStdOut()
    frame.Interact()
    frame.RestoreStdOut()
